今天來講講前幾次也有出現的 LiveData,以下如有解釋不清或是描述錯誤的地方還請大家多多指教:
LiveData 是一個具有生命週期感知的 observable data holder class,只在依附的 LifecycleOwner 是 active 的狀態下才會進行 observer,只有在資料狀態改變時才會通知觀察者,可透過以下兩種方式來更新 LiveData。
setValue(T)
postValue(T)
以下分成 map 和 switchMap,這兩者的差別在於一個回傳物件,一個要回傳 LiveData 物件,那這會用在什麼地方呢,當你需要將監聽的 LiveData 值再轉換成另一個樣子動態通知另一個 view 去做變化:
val cityLiveData: LiveData<CityCard> = CityLiveData()
val cityName: LiveData<String> = Transformations.map(cityLiveData) {
    city -> "${city.name}"
}
val cityLiveData: LiveData<CityCard> = CityLiveData()
val cityName: LiveData<String> = Transformations.switchMap(cityLiveData) { city -> 
		liveData {
        emit(city.cityName)
    }
}
合併多個 LiveData,只要其中一個有變化就會發通知出去,使用情境在於,假如今天你的介面的資料來源有很多個,這時就可以使用 MediatorLiveData 去監聽多個來源的資料變化:
val cityList = MediatorLiveData<List<CityCard>>()
fun setData() {
	cityList.addSource(firstLiveData) { value ->
        cityList.value = value
    }
    cityList.addSource(secondLiveData) { value ->
        cityList.value = value
    }
}
library 的部分依樣在 lifecycle 那邊就已經加好了,直接來設置 LiveData,這邊會使用 private 的 mutableLiveData 來進行 setValue,由外部監聽 LiveData,確保只能由 ViewModel 來變動值,外部只能取得值不能改變值,並包覆之前所寫的 Resource:
// 新增城市
private val _card = MutableLiveData<Resource<CityCard>>()
val card: LiveData<Resource<CityCard>> = _card
// 取得 db 城市列表
private val _cardList = MutableLiveData<Resource<List<CityCard>>>()
val cardList: LiveData<Resource<List<CityCard>>> = _cardList
透過 Resource 傳達出去的狀態,可以顯示資料讀取狀態的 layout
class MainViewModel: ViewModel() {
    private val _isCardInserted = MutableLiveData<Resource<Boolean>>()
    val isCardInserted: LiveData<Resource<Boolean>> = _isCardInserted
    private val _cardList = MutableLiveData<Resource<List<CityCard>>>()
    val cardList: LiveData<Resource<List<CityCard>>> = _cardList
    fun getForecast(country: String) {
        _isCardInserted.value = Resource.Loading()
        val service = WeathbyRetrofit.makeRetrofitService()
        viewModelScope.launch {
            runCatching {
                service.getForecast(query = country)
            }.onSuccess {
                it.body()?.apply {
                   setCardDB(CityEntities(...))
                }
            }.onFailure {
                _isCardInserted.value = Resource.Error(it.message ?: "連線逾時請稍後再試")
            }
        }
    }
    private fun setCardDB(city: CityEntities) {
				val db: CityDAO = CityDatabase.getInstance(WeathApplication.instance.applicationContext).cityDao() 
        viewModelScope.launch {
            runCatching {
                db.insertAll(city)
            }.onSuccess {
                _isCardInserted.value = Resource.Success(true)
            }.onFailure {
                _isCardInserted.value = Resource.Error(it.message ?: "連線逾時請稍後再試")
            }
        }
    }
    fun getCardDB(db: CityDAO) {
        _cardList.value = Resource.Loading()
				val db: CityDAO = CityDatabase.getInstance(WeathApplication.instance.applicationContext).cityDao() 
        viewModelScope.launch {
            runCatching {
                db.getAll()
            }.onSuccess {
                _cardList.value = Resource.Success(
                    it.map { 
                        CityCard(
                            ...
                        )
                    }
                )
            }.onFailure {
                _cardList.value = Resource.Error(it.message ?: "連線逾時請稍後再試")
            }
        }
    }
}
View 可以這樣監聽:
private fun setupViewModel() {
    viewModel.isCardInserted.observe(viewLifecycleOwner) {
        when(it) {
            is Resource.Loading -> {
                showLoading()
            }
            is Resource.Success -> {
                hideLoading()
                if (it.data == true) viewModel.getCardDB()
            }
            is Resource.Error -> {
                hideLoading()
            }
        }
    }
    viewModel.cardList.observe(viewLifecycleOwner) {
        when(it) {
            is Resource.Loading -> {
                showLoading()
            }
            is Resource.Success -> {
                hideLoading()
                it.data?.apply {
                    cardAdapter.submitList(this)
                }
            }
            is Resource.Error -> {
                hideLoading()
            }
        }
    }
}
之後 db 會以注入的方式取得